package OpenToday.PIMHelper;

import OpenToday.Configuration.OpenTodayConfiguration;
import OpenToday.Tools.ExceptionUtils;
import OpenToday.Tools.TimeUtils;
import java.util.*;
import javax.microedition.lcdui.DateField;
import javax.microedition.pim.*;
/*
 * PIMHelper.java
 *
 * Created on 8 ������ 2007, 06:47
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 *
 *Note: This source code is protected under the This software is licensed under the CC-GNU GPL.
 */

/**
 *
 * @author Menny Even Danan (c) 2007. Note: This source code is protected under the This software is licensed under the CC-GNU GPL.
 */
public class PIMHelper
{
    private static PIMHelper ms_Instance;
    
    public synchronized static PIMHelper getInstance()  throws PIMException
    {
	if (ms_Instance == null)
	    ms_Instance = new PIMHelper();
	
	return ms_Instance;
    }
    
    private final EventList m_phoneEvents;
    private final ToDoList m_phoneToDos;
    /** Creates a new instance of PIMHelper */
    private PIMHelper()  throws PIMException
    {
	//calendar
	m_phoneEvents = (EventList) PIM.getInstance().openPIMList(PIM.EVENT_LIST, PIM.READ_ONLY);
	//tasks
	m_phoneToDos = (ToDoList) PIM.getInstance().openPIMList(PIM.TODO_LIST, PIM.READ_ONLY);
    }
    
    public IPhoneCalendarEvents createCurrentCalendarStatus() throws Exception
    {
	return new PhoneCalendarEvents(m_phoneEvents, m_phoneToDos);
    }

    public void Destroy()
    {
	try
	{
	    m_phoneEvents.close();
	} 
	catch (PIMException ex)
	{
	    ex.printStackTrace();
	}
	
	try
	{
	    m_phoneToDos.close();
	} 
	catch (PIMException ex)
	{
	    ex.printStackTrace();
	}
    }
    
    private class PhoneCalendarEvents implements IPhoneCalendarEvents
    {
	private final Vector m_days;
	private final IPhoneCalendarDayEvents m_overdues;
	
	public PhoneCalendarEvents(EventList events, ToDoList toDos) throws Exception
	{
	    OpenTodayConfiguration cnf = OpenTodayConfiguration.getInstance();
	    int daysToShow = cnf.get_DaysAheadToLook();
	    int totalEntriesToShow = cnf.get_MaxEventsToShow();
	    boolean showEventFromStartOfToday = cnf.get_ShowEventsFromStartOfToday();
	    boolean showPrivateItems = cnf.get_ShowPrivateEvents();
	    // calculcate today's date and next week's date
	    long current_time = TimeUtils.GetCurrentTime().getTime();
	    long current_day = TimeUtils.CalcualteRoundDate(current_time);
	    long next_week = current_day + (daysToShow * TimeUtils.MS_PER_DAY) - 1;//almost next week
	    m_days = new Vector();
	    //filling with empty containers
	    for(int dayIndex=0; dayIndex<daysToShow; dayIndex++)
	    	m_days.addElement(new PhoneCalendarDayEvents(current_day + (dayIndex*TimeUtils.MS_PER_DAY), dayIndex));
	    m_overdues = new PhoneCalendarDayEvents(current_day - TimeUtils.MS_PER_DAY, getOverdueDayIndex());
	    
	    ExceptionUtils.PHASE("start PhoneCalendarEvents");
	    
	    // Get the enumeration of matching elements
	    ExceptionUtils.PHASE("PhoneCalendarEvents get pims items");
	    //last boolean asks if to return ANY recurring event (false) or just the initial event (true).
	    //'true' does return much faster from the 'items()' call.
	    Enumeration this_week_events = events.items(EventList.OCCURRING, showEventFromStartOfToday? current_day : current_time, next_week, true);
	    
	    long currentDay = 0;
	    int entriesLeftToShow = totalEntriesToShow;
	    ExceptionUtils.PHASE("PhoneCalendarEvents events loop");
	    while(this_week_events.hasMoreElements() && (entriesLeftToShow>0))
	    {
		ExceptionUtils.PHASE("PhoneCalendarEvents events " + entriesLeftToShow);
		entriesLeftToShow--;
		Event event = (Event)this_week_events.nextElement();
		try
		{
		    ExceptionUtils.PHASE("PhoneCalendarEvents create event");
		    PhoneCalendarEvent newEvent = new PhoneCalendarEvent(event);
		    if ((!showPrivateItems) && newEvent.isPrivateEvent())
			continue;//not showing privates
		    ExceptionUtils.PHASE("PhoneCalendarEvents event created");
		    long day = TimeUtils.CalculateDaysDiff(current_day, newEvent.getEventStartTime().getTime());
		    int dayIndex = day>0? (int)day : 0;
		    //getting the container for this day
		    PhoneCalendarDayEvents thisDayEvents = (PhoneCalendarDayEvents)m_days.elementAt(dayIndex);
		    thisDayEvents.AddEvent(newEvent);
		}
		catch(Exception exception)
		{
		    if (!OpenTodayConfiguration.getInstance().getDiscardErrors())
			throw exception;
		}
	    }
	    
	    // Get the enumeration of matching elements
	    ExceptionUtils.PHASE("PhoneCalendarEvents get todos items");
	    //for tasks, we'll check ALL items (how many can it be?) and find the ones that interest us
	    int overdueDayIndex = getOverdueDayIndex();
	    boolean showDoneTasks = cnf.getShowDoneTasks();
	    Enumeration all_todos = toDos.items();
	    while(all_todos.hasMoreElements())
	    {
		ToDo todo = (ToDo)all_todos.nextElement();
		try
		{
		    PhoneCalendarToDo newToDo = new PhoneCalendarToDo(todo);
		    long todoDueTime = newToDo.getDueTime().getTime();
		    if (todoDueTime > next_week)
			continue;//to far in the future!
		    if ((!showPrivateItems) && newToDo.isPrivateToDo())
			continue;//not showing privates
		    if ((!showDoneTasks) && newToDo.isDone())
			continue;//not showing done tasks

		    long day = TimeUtils.CalculateDaysDiff(current_day, todoDueTime);
		    int dayIndex = (day>=0)? (int)day : overdueDayIndex;
		    if ((dayIndex == overdueDayIndex) && (newToDo.isDone()))
			continue;//it is done, not overdue
		    //getting the container for this day
		    PhoneCalendarDayEvents thisDayEvents = (PhoneCalendarDayEvents)getEventsForDay(dayIndex);
		    thisDayEvents.AddToDo(newToDo);
		}
		catch(Exception exception)
		{
		    if (!OpenTodayConfiguration.getInstance().getDiscardErrors())
			throw exception;
		}
	    }
	}
	
	public boolean getHasEventsToday()
	{
	    if (m_days.size() > 0)
	    {
		IPhoneCalendarDayEvents today = (IPhoneCalendarDayEvents)m_days.elementAt(0);
		return (today.getEventsCount() + today.getToDosCount()) > 0;
	    }
	    else return false;//no element -> no events
	}
	
	public boolean getHasOverdueItems()
	{
	    return (m_overdues.getEventsCount() + m_overdues.getToDosCount()) > 0;
	}
    
	public int getOverdueDayIndex()
	{
	    return -1;
	}
    
	public int getMaximumDayIndex()
	{
	    return m_days.size();
	}

	public IPhoneCalendarDayEvents getEventsForDay(int dayIndex)
	{
	    if (dayIndex == getOverdueDayIndex())
		return m_overdues;
	    else
		return (IPhoneCalendarDayEvents)m_days.elementAt(dayIndex);
	}

	public long getEventsCheckSum()
	{
	    long checkSum = 0;
	    for(int i = 0; i< getMaximumDayIndex(); i++)
	    {
		checkSum += ((i+1) + ((PhoneCalendarDayEvents)getEventsForDay(i)).get_EventsCheckSum());
	    }
	    return checkSum;
	}
	
	private class PhoneCalendarToDo implements IPhoneToDo
	{
	    private final Date m_dueTime;
	    private final String m_todoTitle;
	    private final boolean m_isPrivateToDo;
	    private final boolean m_isComplededToDo;
	    
	    public PhoneCalendarToDo(ToDo todo)
	    {
		ExceptionUtils.PHASE("PhoneCalendarToDo ctor");
		ExceptionUtils.PHASE("PhoneCalendarToDo getFields");
		int[] fields = todo.getFields();
		ExceptionUtils.PHASE("PhoneCalendarToDo start");
		//due time
		long eventStartTimeInMS = todo.getDate(todo.DUE, PIMItem.ATTR_NONE);
		m_dueTime = new Date();
		m_dueTime.setTime(eventStartTimeInMS);
		//title
		if (hasField(fields, ToDo.SUMMARY))//summery is the best
		    m_todoTitle = todo.getString(ToDo.SUMMARY, PIMItem.ATTR_NONE);
		else
		{//if we do not have a summary, we'll try the NOTE
		    if (hasField(fields, Event.NOTE))
			m_todoTitle = todo.getString(ToDo.NOTE, PIMItem.ATTR_NONE);
		    else//Nothing?! what now?
			m_todoTitle = "";
		}
		//private?
		if (hasField(fields, ToDo.CLASS))
		{
		    ExceptionUtils.PHASE("PhoneCalendarToDo CLASS");
		    m_isPrivateToDo = (todo.getInt(ToDo.CLASS, 0) == ToDo.CLASS_PRIVATE);
		}
		else
		    m_isPrivateToDo = false;
		//completed?
		if (hasField(fields, ToDo.COMPLETED))
		{
		    ExceptionUtils.PHASE("PhoneCalendarToDo COMPLETED");
		    m_isComplededToDo = todo.getBoolean(ToDo.COMPLETED, PIMItem.ATTR_NONE);
		}
		else
		{
		    //if there is a completion date, it must be complete
		    if (hasField(fields, ToDo.COMPLETION_DATE))
		    {
			ExceptionUtils.PHASE("PhoneCalendarToDo COMPLETION_DATE");
			m_isComplededToDo = todo.getDate(ToDo.COMPLETION_DATE, PIMItem.ATTR_NONE) > 0;
		    }
		    else
		    {
			m_isComplededToDo = false;
		    }
		}
		
	    }
	    
	    private boolean hasField(int[] fields, int field)
	    {
		ExceptionUtils.PHASE("PhoneCalendarToDo hasField fields " + fields.length + " " + field);
		for(int i=0; i<fields.length; i++)
		{
		    int val = fields[i];
		    ExceptionUtils.PHASE("PhoneCalendarToDo hasField @ " + i + " " + val);
		    if (val == field)
			return true;
		}
		
		return false;
	    }
	    
	    public Date getDueTime() 
	    {
		return m_dueTime;
	    }

	    public boolean isPrivateToDo() 
	    {
		return m_isPrivateToDo;
	    }

	    public String getToDoTitle() 
	    {
		return m_todoTitle;
	    }

	    public boolean isDone() 
	    {
		return m_isComplededToDo;
	    }
	    
	    public long get_CheckSum()
	    {
		int isDoneAddition =  isDone()? 1 : 0;
		int isPrivateAddition =  isPrivateToDo()? 1 : 0;
		return  isDoneAddition + isPrivateAddition + 
                                m_dueTime.getTime() + 
                                m_todoTitle.hashCode();
	    }
	}
	     
	private class PhoneCalendarEvent implements IPhoneCalendarEvent
	{	    
	    private final Date m_eventStartTime;
	    private final Date m_eventEndTime;
	    private final String m_eventTitle;
	    private final int m_eventType;
	    private final boolean m_isPrivateEvent;
	    
	    public PhoneCalendarEvent(Event e)
	    {
		ExceptionUtils.PHASE("PhoneCalendarEvent ctor");
		ExceptionUtils.PHASE("PhoneCalendarEvent getFields");
		int[] fields = e.getFields();
		ExceptionUtils.PHASE("PhoneCalendarEvent start");
		long eventStartTimeInMS = e.getDate(Event.START, PIMItem.ATTR_NONE);
		m_eventStartTime = new Date();
		m_eventStartTime.setTime(eventStartTimeInMS);
		m_eventEndTime = new Date();
		ExceptionUtils.PHASE("PhoneCalendarEvent end");
		if (hasField(fields, Event.END))
		    m_eventEndTime.setTime(e.getDate(Event.END, PIMItem.ATTR_NONE));
		else
		    m_eventEndTime.setTime(eventStartTimeInMS + 1);
		
		if (hasField(fields, Event.SUMMARY))//summery is the best
		    m_eventTitle = e.getString(Event.SUMMARY, PIMItem.ATTR_NONE);
		else
		{//if we do not have a summary, we'll try the NOTE
		    if (hasField(fields, Event.NOTE))
			m_eventTitle = e.getString(Event.NOTE, PIMItem.ATTR_NONE);
		    else//Nothing?! what now?
			m_eventTitle = "";
		}
		
		if (hasField(fields, Event.EXTENDED_FIELD_MIN_VALUE))
		{
		    ExceptionUtils.PHASE("PhoneCalendarEvent EXTENDED_FIELD_MIN_VALUE");
		    m_eventType = e.getInt(Event.EXTENDED_FIELD_MIN_VALUE, PIMItem.ATTR_NONE);
		}
		else //workaround for my phone (k610i)
                //an all day event starts at 00:00 and last for ?? seconds
                if ((m_eventEndTime.getTime() - m_eventStartTime.getTime()) == 86339000)
                    m_eventType = 2;
                else
		    m_eventType = 0;
                
		if (hasField(fields, Event.CLASS))
		{
		    ExceptionUtils.PHASE("PhoneCalendarEvent CLASS");
		    m_isPrivateEvent = (e.getInt(Event.CLASS, 0) == Event.CLASS_PRIVATE);
		}
		else
		    m_isPrivateEvent = false;
	    }
	    
	    private boolean hasField(int[] fields, int field)
	    {
		ExceptionUtils.PHASE("hasField fields " + fields.length + " " + field);
		for(int i=0; i<fields.length; i++)
		{
		    int val = fields[i];
		    ExceptionUtils.PHASE("hasField @ " + i + " " + val);
		    if (val == field)
			return true;
		}
		
		return false;
	    }
	    
	    public Date getEventStartTime()
	    {
		return m_eventStartTime;
	    }
	    
	     public Date getEventEndTime()
	    {
		return m_eventEndTime;
	    }
	     
	     public boolean isAllDayEvent()
	     {
		 return m_eventType == 2;
	     }

	    public boolean isCurrentEvent()
	    {
                if (isAllDayEvent())
		    return false;//all day events are never the current
		else
                {
		    long currentTime = (new Date()).getTime();
                    return ((currentTime >= m_eventStartTime.getTime()) && (currentTime < m_eventEndTime.getTime()));
                }
	    }

	    public String getEventTitle()
	    {
                return m_eventTitle;
	    }

	    public long get_EventsCheckSum()
	    {
		int isCurrentAddition =  isCurrentEvent()? 1 : 0;
		int isPrivateAddition =  isPrivateEvent()? 1 : 0;
		int isAllDayAddition =  isAllDayEvent()? 1 : 0;
		return  isCurrentAddition + isPrivateAddition + isAllDayAddition +
                                m_eventStartTime.getTime() + 
                                m_eventEndTime.getTime() + 
                                m_eventTitle.hashCode() + 
                                m_eventType;
	    }

	    public boolean isPrivateEvent()
	    {
		return m_isPrivateEvent;
	    }
	}
	
	private class PhoneCalendarDayEvents implements IPhoneCalendarDayEvents
	{
	    private final Date m_date;
	    private final Vector m_events;
	    private final Vector m_todos;
	    private final int m_dayIndex;
	    
	    public PhoneCalendarDayEvents(long date, int day)
	    {
		m_date = new Date();
		m_date.setTime(date);
		
		m_events = new Vector();
		m_todos = new Vector();
		
		m_dayIndex = day;
	    }
	    
	    public Date getDayDate()
	    {
		return m_date;
	    }

	    public int getEventsCount()
	    {
		return m_events.size();
	    }
	    
	    public int getToDosCount()
	    {
		return m_todos.size();
	    }
	    
	    public int getDayOffset()
	    {
		return m_dayIndex;
	    }

	    public IPhoneCalendarEvent getEventAtIndex(int eventIndex)
	    {
		ExceptionUtils.PHASE("getEvent " + eventIndex + "/" + m_events.size());
		return (IPhoneCalendarEvent)m_events.elementAt(eventIndex);
	    }

	    public IPhoneToDo getToDoAtIndex(int toDoIndex)
	    {
		ExceptionUtils.PHASE("getToDo " + toDoIndex + "/" + m_todos.size());
		return (IPhoneToDo)m_todos.elementAt(toDoIndex);
	    }
	    
	    public void AddEvent(IPhoneCalendarEvent newEvent)
	    {
		m_events.addElement(newEvent);
	    }
	    
	    public void AddToDo(IPhoneToDo newToDo)
	    {
		m_todos.addElement(newToDo);
	    }

	    public long get_EventsCheckSum()
	    {
		long checkSum = m_dayIndex + (m_date.getTime() / TimeUtils.MS_PER_DAY);
		for(int i = 0; i< getEventsCount(); i++)
		{
		    checkSum += ((i+1) + ((PhoneCalendarEvent)getEventAtIndex(i)).get_EventsCheckSum());
		}
		for(int i = 0; i< getToDosCount(); i++)
		{
		    checkSum += ((i+1) + ((PhoneCalendarToDo)getToDoAtIndex(i)).get_CheckSum());
		}
		return checkSum;
	    }
	}
    }
}